package com.hwtx.form.domain.handle.core;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.def.FormDetailExt;
import com.hwtx.form.domain.ds.DataSourceContextHolder;
import com.hwtx.form.domain.handle.SelectItemsInfo;
import com.hwtx.form.domain.handle.datamodel.DataModel;
import com.hwtx.form.domain.handle.datamodel.DataModelRelate;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.vo.FormRepData;
import com.hwtx.form.persistence.DataSourceRepo;
import com.hwtx.form.persistence.entity.FormDefDetail;
import com.hwtx.form.query.FormValueQuery;
import com.hwtx.form.util.FormUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.DEFAULT_TABLE_NAME;
import static java.util.stream.Collectors.toMap;

@Component
@Slf4j
public class DefaultFormDataReadHandle extends DefaultFormBaseHandle {

    SelectItemsInfo.ComponentItemParser parser = new SelectItemsInfo.DefaultComponentItemParser();
    @Resource
    private DataModelRepo dataModelRepo;
    @Resource
    private DataSourceRepo dataSourceRepo;
    @Resource
    private JdbcTemplate jdbcTemplate;

    public FormRepData.MapFormRepData getFormData(FormDef formDef, FormValueQuery formValueQuery) {
        Long id = formValueQuery.getValueIds().getFirst();
        if (id == null) {
            throw new RuntimeException("查询id不能为空");
        }
        try {
            FormDefDetail formDefDetail = formConfigMeta.getFormDefDetail(formDef.getFormId());
            String modelName = formDefDetail.getModelName();
            List<DataModelRelate> relates = dataModelRepo.getRelates(modelName);
            SelectItemsInfo selectItemsInfo = getFormItems(formDef.getFormId(), false);
            FormDetailExt.FormExt formExt = FormUtil.getFormDatailExtOfFormExt(formDefDetail.getExt());
            List<SelectItemsInfo.SelectItemInfo> items = selectItemsInfo.getNoTableItems();
            DataModel dataModel = dataModelRepo.getDataModel(modelName);
            if (dataModel == null) {
                throw new RuntimeException("数据模型不存在");
            }
            if (CollectionUtils.isEmpty(relates)) {
                if (CollectionUtils.isNotEmpty(selectItemsInfo.getTableItems().get(dataModel.getMaster()))) {
                    items.addAll(selectItemsInfo.getTableItems().get(dataModel.getMaster()));
                }
                return getFormData(id, dataModel, formExt, items, formValueQuery.getParameters());
            }
            return getRelateFormData(id, dataModel, formExt, relates, selectItemsInfo, formValueQuery.getParameters());
        } catch (Exception e) {
            log.info("DefaultFormDataReadHandle getFormData error", e);
        }
        return new FormRepData.MapFormRepData(Maps.newHashMap());
    }

    private FormRepData.MapFormRepData getRelateFormData(Object id, DataModel dataModel, FormDetailExt.FormExt formExt, List<DataModelRelate> relates,
                                                         SelectItemsInfo selectItemsInfo, Map<String, String[]> requestParams) {
        try {
            FilterAndParameters filterAndParameters = getFilterAndParameters(id, formExt, requestParams);
            List<String> filterItems = filterAndParameters.filterItems;
            List<Object> parameters = filterAndParameters.parameters;
            checkJdbcTemplate(dataModel.getDs());
            Map<String, Map<String, String>> extraColumns = Maps.newHashMap();
            Map<String, Object> ret = Maps.newHashMap();
            final AtomicBoolean hasAdd = new AtomicBoolean(false);
            Set<SelectItemsInfo.SelectItemInfo> hasAddItems = Sets.newHashSet();
            Set<SelectItemsInfo.SelectItemInfo> hasComponentItems = Sets.newHashSet();
            selectItemsInfo.getNoTableItems().forEach(item -> {
                Map<String, Map<String, String>> addFields = parser.readFields(item);
                if (addFields != null) {
                    if (addFields.containsKey(DEFAULT_TABLE_NAME)) {
                        extraColumns.put(dataModel.getMaster(), addFields.get(DEFAULT_TABLE_NAME));
                    } else {
                        extraColumns.putAll(addFields);
                    }
                    hasAdd.set(true);
                    if (MapUtils.isNotEmpty(item.getAddFields())) {
                        hasAddItems.add(item);
                    }
                }
                if (item.getFormComponentConfig() != null) {
                    hasComponentItems.add(item);
                }
            });
            Map<String, List<String>> fieldsAndTables = Maps.newHashMap();
            selectItemsInfo.getTableItems().forEach((table, fields) -> {
                fields.forEach(field -> fieldsAndTables.compute(field.getField(), (k, v) -> {
                    if (v == null) {
                        v = Lists.newArrayList();
                    }
                    v.add(table);
                    return v;
                }));
            });
            String sql = dataSourceRepo.buildRelateSql(dataModel, relates, selectItemsInfo.getTableItems(), extraColumns, filterItems, fieldsAndTables);
            Map<String, Object> data = jdbcTemplate.queryForMap(sql, parameters.toArray());
            ret.putAll(data);
            if (hasAdd.get()) {
                hasAddItems.forEach(item -> {
                    String key = StringUtils.isNotBlank(item.getName()) ? item.getName() : item.getField();
                    List<String> fields = item.getAddFields().values().stream().flatMap(Collection::stream).collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(fields)) {
                        String retValue = fields.stream().map(data::get).map(String::valueOf).collect(Collectors.joining(","));
                        ret.put(key, retValue);
                    }
                });
            }
            hasComponentItems.forEach(item -> fillInvokeValue(data, requestParams, ret, item));
            return new FormRepData.MapFormRepData(ret);
        } finally {
            DataSourceContextHolder.clearDataSourceKey();
        }
    }

    private FormRepData.MapFormRepData getFormData(Object id, DataModel dataModel, FormDetailExt.FormExt formExt, List<SelectItemsInfo.SelectItemInfo> handleItems,
                                                   Map<String, String[]> requestParams) {
        FilterAndParameters filterAndParameters = getFilterAndParameters(id, formExt, requestParams);
        List<String> filterItems = filterAndParameters.filterItems;
        List<Object> parameters = filterAndParameters.parameters;
        try {
            String table = dataModel.getMaster();
            checkJdbcTemplate(dataModel.getDs());
            Map<String, String> columns = Maps.newHashMap();
            Map<String, Object> ret = Maps.newHashMap();
            Set<SelectItemsInfo.SelectItemInfo> hasAddItems = Sets.newHashSet();
            List<SelectItemsInfo.SelectItemInfo> hasComponentItems = Lists.newArrayList();
            final AtomicBoolean hasAdd = new AtomicBoolean(false);
            handleItems.forEach(item -> {
                Map<String, Map<String, String>> tableFields = parser.readFields(item);
                Map<String, String> fields = tableFields.get(table);
                if (fields == null) {
                    fields = tableFields.get(DEFAULT_TABLE_NAME);
                }
                if (MapUtils.isNotEmpty(fields)) {
                    hasAdd.set(true);
                    columns.putAll(fields);
                    if (MapUtils.isNotEmpty(item.getAddFields())) {
                        hasAddItems.add(item);
                    }
                }
                if (item.getFormComponentConfig() != null) {
                    hasComponentItems.add(item);
                }
            });
            String sql = dataSourceRepo.buildSelectSql(table, columns, filterItems);
            Map<String, Object> data = jdbcTemplate.queryForMap(sql, parameters.toArray());
            ret.putAll(data);
            if (hasAdd.get()) {
                hasAddItems.forEach(item -> {
                    String key = StringUtils.isNotBlank(item.getName()) ? item.getName() : item.getField();
                    List<String> fields = item.getAddFields().get(table);
                    ret.put(key, fields.stream().map(data::get).map(String::valueOf).collect(Collectors.joining(",")));
                });
            }
            hasComponentItems.forEach(item -> fillInvokeValue(data, requestParams, ret, item));
            return new FormRepData.MapFormRepData(ret);
        } finally {
            DataSourceContextHolder.clearDataSourceKey();
        }
    }

    private void fillInvokeValue(Map<String, Object> data, Map<String, String[]> requestParams, Map<String, Object> ret,
                                 SelectItemsInfo.SelectItemInfo item) {
        Map<String, Object> invokeValue = item.getFormComponentConfig().invokeReadService(item, data, requestParams);
        if (invokeValue != null) {
            if (MapUtils.isNotEmpty(item.getAddFields())) {
                String key = StringUtils.isNotBlank(item.getName()) ? item.getName() : item.getField();
                ret.put(key, invokeValue.values().stream().map(Object::toString).collect(Collectors.joining(",")));
            } else {
                ret.putAll(invokeValue);
            }
        }
    }

    private FilterAndParameters getFilterAndParameters(Object id, FormDetailExt.FormExt formExt, Map<String, String[]> requestParams) {
        List<String> filterItems = Lists.newArrayList();
        List<Object> parameters = Lists.newArrayList();
        if (formExt != null && formExt.getParamMapping() != null) {
            Map<String, String> paramMapping = formExt.getParamMapping();
            paramMapping.forEach((k, v) -> {
                filterItems.add(k);
                parameters.add(FormUtil.getSearchValue(requestParams, k));
            });
        } else {
            filterItems.add(FormConstants.DefaultColumn.id.name());
            parameters.add(id);
        }
        return new FilterAndParameters(filterItems, parameters);
    }

    public List<Map<String, Object>> getCommonFormData(FormDef formDef, Map<String, Object> filters) {
        try {
            FormDefDetail formDefDetail = formConfigMeta.getFormDefDetail(formDef.getFormId());
            SelectItemsInfo itemInfo = getFormItems(formDef.getFormId(), true);
            String modelName = formDefDetail.getModelName();
            List<String> filterItems = Lists.newArrayList(filters.keySet());
            DataModel dataModel = dataModelRepo.getDataModel(modelName);
            if (dataModel == null) {
                throw new RuntimeException("数据模型不存在");
            }
            checkJdbcTemplate(dataModel.getDs());
            List<SelectItemsInfo.SelectItemInfo> items = List.of();
            if (MapUtils.isNotEmpty(itemInfo.getTableItems())) {
                items = itemInfo.getTableItems().get(dataModel.getMaster());
            }
            String sql = dataSourceRepo.buildSelectSql(dataModel.getMaster(), items.stream().collect(toMap(SelectItemsInfo.SelectItemInfo::getField, SelectItemsInfo.SelectItemInfo::getField)), filterItems);
            return jdbcTemplate.queryForList(sql, filterItems.stream().map(filters::get).toArray());
        } catch (ExecutionException e) {
            log.info("DefaultFormDataHandle getCommonFormData error", e);
        } finally {
            DataSourceContextHolder.clearDataSourceKey();
        }
        return Lists.newArrayList();
    }

    record FilterAndParameters(List<String> filterItems, List<Object> parameters) {
    }
}
